﻿Ext.EventManager = {
    //  /^(?:capture|scope|……|fireClickEvent)$/     正则表达式对象，以（开头，以）结尾，括号里面可以是众选项之一
    optionsRe: /^(?:capture|scope|delay|buffer|single|stopEvent|disableLocking|preventDefault|stopPropagation|normalized|args|delegate|horizontal|vertical|dragThreshold|holdThreshold|doubleTapThreshold|cancelThreshold|singleTapThreshold|fireClickEvent)$/,
    //  /^(?:pinch|pinchstart|……|tapcancel)$/i      正则表达式对象，以（开头，以）结尾，括号里面可以是众选项之一，忽略大小写
    touchRe: /^(?:pinch|pinchstart|pinchend|tap|singletap|doubletap|swipe|swipeleft|swiperight|drag|dragstart|dragend|touchdown|touchstart|touchmove|touchend|taphold|tapstart|tapcancel)$/i,

    //加入一个事件处理函数，方法on是其简写方式。
    //element:要分配的html元素或者其id。
    //eventName:侦听事件的名称。
    //fn:事件处理函数。
    //scope:（可选的）事件处理函数执行时所在的作用域。默认为该 Element.
    //o:（可选的） 包含句柄配置属性的一个对象。
    addListener: function (element, eventName, fn, scope, o) {

        if (Ext.isObject(eventName)) {//事件名为对象，说明采用的不是标准方式的注册监听方式
            this.handleListenerConfig(element, eventName);//将非标准的方式转换成标准的方式，转换后会调用addListener方法
            return;
        }

        var dom = Ext.getDom(element); //返回dom对象,类型是HTMLElement 


        if (!dom) {//元素不存在
            throw "Error listening for \"" + eventName + '\". Element "' + element + '" doesn\'t exist.';
        }

        if (!fn) {//监听函数不存在
            throw 'Error listening for "' + eventName + '". No handler function specified';
        }

        var touch = this.touchRe.test(eventName);//判断eventName是否匹配touchRe正则表达式的模式

        //生成一个函数，该函数有两个参数：e, args。其中e是传入的事件对象，它将在函数中被封装成一个全新的事件对象，
        //args在生成事件对象时用到，生成的函数的函数体根据createListenerWrap中传入的参数生成，不同的参数会生成不同的函数体。
        var wrap = this.createListenerWrap(dom, eventName, fn, scope, o, touch);

        //getEventListenerCache获得Ext.cache中“dom”对应的events对象的“eventName”为名的对象，
        //包装一个新的对象，加入其中
        this.getEventListenerCache(dom, eventName).push({
            fn: fn,
            wrap: wrap,
            scope: scope
        });

        if (touch) {//如果符合模式
            Ext.gesture.Manager.addEventListener(dom, eventName, wrap, o);
        }
        else {
            // 将事件监听器注册到实际的元素上
            o = o || {};
            //addEventListener(事件名, 响应函数, 是否使用捕获模式);
            dom.addEventListener(eventName, wrap, o.capture || false);
        }
    },

    //移除事件处理器（event handler），跟简写方式un是一样的。通常你会在元素上直接使用Ext.Element.removeListener来调用此方法。
    //element:欲移除事件的html元素或id。
    //eventName:事件的类型。
    //fn:要移除的事件处理函数。
    //scope:（可选的）事件处理函数执行时所在的作用域。如果添加时间监听函数时定义了作用域，那么scope必须指向相同的对象
    removeListener: function (element, eventName, fn, scope) {

        if (Ext.isObject(eventName)) {//事件名为对象，说明采用的不是标准方式的注册监听方式
            this.handleListenerConfig(element, eventName, true); //将非标准的方式转换成标准的方式，转换后会调用removeListener方法
            return;
        }

        var dom = Ext.getDom(element),
            cache = this.getEventListenerCache(dom, eventName),
            i = cache.length, j,
            listener, wrap, tasks;

        while (i--) {
            listener = cache[i];

            if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
                wrap = listener.wrap;


                if (wrap.task) {
                    clearTimeout(wrap.task);
                    delete wrap.task;
                }


                j = wrap.tasks && wrap.tasks.length;
                if (j) {
                    while (j--) {
                        clearTimeout(wrap.tasks[j]);
                    }
                    delete wrap.tasks;
                }

                if (this.touchRe.test(eventName)) {
                    Ext.gesture.Manager.removeEventListener(dom, eventName, wrap);
                }
                else {

                    dom.removeEventListener(eventName, wrap, false);
                }


                cache.splice(i, 1);
            }
        }
    },

    //移除元素的所有事件.常用Ext.Element的removeAllListeners方法调用该方法
    removeAll: function (element) {
        var dom = Ext.getDom(element),
            cache = this.getElementEventCache(dom),
            ev;

        for (ev in cache) {
            if (!cache.hasOwnProperty(ev)) {
                continue;
            }
            this.removeListener(dom, ev);
        }
        Ext.cache[dom.id].events = {};
    },

    purgeElement: function (element, recurse, eventName) {
        var dom = Ext.getDom(element),
            i = 0, len;

        if (eventName) {
            this.removeListener(dom, eventName);
        }
        else {
            this.removeAll(dom);
        }

        if (recurse && dom && dom.childNodes) {
            for (len = element.childNodes.length; i < len; i++) {
                this.purgeElement(element.childNodes[i], recurse, eventName);
            }
        }
    },

    //将非标准的配置方式统一转换成标准的配置方式
    //element:要分配的html元素或者其id。
    //eventName:侦听事件的名称，有时候为对象
    //remove:为true时移除监听函数，为false时添加监听函数，默认为false
    handleListenerConfig: function (element, config, remove) {
        var key, value;

        // 遍历config的属性
        for (key in config) {
            if (!config.hasOwnProperty(key)) {//如果config没有这个属性，则跳出继续下一个循环
                continue;
            }
            // 如果key不是一个事件选项
            if (!this.optionsRe.test(key)) {
                value = config[key];

                // 如果value是函数，则说明config为{ click: function(){}, mouseout: function(){}, scope: this}的形式，
                // value是监听函数，它与其他跟它并列的监听函数一起共用配置属性，比如共用scope
                if (Ext.isFunction(value)) {
                    // 接下来采用标准的配置方式：addListener(元素, 事件名, 监听函数, 作用域, 配置项)
                    this[(remove ? 'remove' : 'add') + 'Listener'](element, key, value, config.scope, config);
                }
                // 如果value不是函数，则说明config为{ click: {fn: function(){}, scope: this }, mouseout: { …… } }的形式，
                // value是{fn: function(){}, scope: this }这样的对象，里面是私有配置信息
                else {
                    // 接下来采用标准的配置方式：addListener(元素, 事件名, 监听函数, 作用域, 配置项)
                    this[(remove ? 'remove' : 'add') + 'Listener'](element, key, config.fn, config.scope, config);
                }
            }
        }
    },

    // 根据element生成Ext内部规则的id，将元素和id都放入Ext.cache中保存，返回生成后的id
    getId: function (element) {
        // if we bind listeners to either the document or the window
        // we have to put them in their own id cache since they both
        // can't get id's on the actual element
        var skip = false,
            id;

        element = Ext.getDom(element);//得到的是HtmlElement

        if (element === document || element === window) {
            skip = true;
        }

        id = Ext.id(element); // 对页面元素生成唯一id，如果该元素已存在id，则不会再生成

        if (!Ext.cache[id]) {//如果在Ext.cache对象中还没有存在名为id值的属性

            /***************** 在EL中addToCache的定义是这样的：******************************************
            El.addToCache = function (el, id) {//在Ext.cache中注册以id值为名，以包括了el的对象为值的属性
                id = id || el.id;
                Ext.cache[id] = {
                    el: el,
                    data: {},
                    events: {}
                };
                return el;
            };
            *********************************************************************************************/

            Ext.Element.addToCache(new Ext.Element(element), id);
            if (skip) {
                Ext.cache[id].skipGarbageCollection = true;
            }
        }
        return id;
    },

    //返回一个新函数，该函数能以浏览器event对象为参数，函数体将浏览器event对象转换为新的event对象，
    //并且执行fn.call(scope || dom, e, t, o)，其中e已经转换成新的event对象
    //dom：htmlElement对象
    //ename：事件名
    //fn：事件响应函数
    //scope：fn作用域
    //o：注册事件的配置项
    //touch：一个布尔值，判断事件名是否符合 在开始定义的touchRe正则表达式对象的模式，不符合就是自定义的
    createListenerWrap: function (dom, ename, fn, scope, o, touch) {
        o = !Ext.isObject(o) ? {} : o;

        var f = ["if(!window.Ext) {return;}"];

        if (touch) {//如果符合EvenManager定义的touchRe正则表达式对象的模式
            f.push('e = new Ext.TouchEventObjectImpl(e, args);');
        }
        else {
            if (o.buffer || o.delay) {//如果指定缓冲运行或推迟运行
                f.push('e = new Ext.EventObjectImpl(e);');
            } else {
                f.push('e = Ext.EventObject.setEvent(e);');//把原始Event对象封装成EventObject对象
            }
        }

        if (o.delegate) {//如果指定了CSS查询器的表达式
            f.push('var t = e.getTarget("' + o.delegate + '", this);');
            f.push('if(!t) {return;}');
        } else {
            f.push('var t = e.target;');
        }

        if (o.target) {//
            f.push('if(e.target !== o.target) {return;}');
        }

        if (o.stopEvent) {//如果o.stopEvent指定为true，就会进行preventDefault、stopProgation两个操作
            f.push('e.stopEvent();');
        } else {
            if (o.preventDefault) {
                f.push('e.preventDefault();');//阻止默认的动作发生
            }
            if (o.stopPropagation) {
                f.push('e.stopPropagation();');//阻止事件冒泡向上传递
            }
        }

        if (o.normalized === false) {//如果指定为false，就把浏览器事件event传到监听函数中去
            f.push('e = e.browserEvent;');
        }

        if (o.buffer) {//指定函数推迟的事件，在没有运行之后，后来的注册函数可以修改它
            f.push('(wrap.task && clearTimeout(wrap.task));');
            f.push('wrap.task = setTimeout(function(){');
        }

        if (o.delay) {//指定该函数推迟多长时间后才运行（以ms为单位）
            f.push('wrap.tasks = wrap.tasks || [];');
            f.push('wrap.tasks.push(setTimeout(function(){');
        }


        f.push('fn.call(scope || dom, e, t, o);');

        if (o.single) {//指定该函数是否只运行一次
            f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
        }

        if (o.delay) {//指定该函数推迟多长时间后才运行（以ms为单位）
            f.push('}, ' + o.delay + '));');
        }

        if (o.buffer) {//指定函数推迟的事件，在没有运行之后，后来的注册函数可以修改它
            f.push('}, ' + o.buffer + ');');
        }

        //e：返回函数的参数， args：返回函数的参数
        var gen = new Function('e', 'o', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join("\n"));

        return function (e, args) {
            //dom：HtmlElement对象, o：配置对象, fn：监听函数, scope：作用域, ename：事件名, dom：HtmlElement对象
            gen.call(dom, e, o, fn, scope, ename, dom, arguments.callee, args);
        };
    },

    //获得Ext.cache中element对应的events对象的eventName
    getEventListenerCache: function (element, eventName) {
        var eventCache = this.getElementEventCache(element);
        return eventCache[eventName] || (eventCache[eventName] = []);
    },

    //返回在 Ext.cache对象中element对应的events对象，该对象是Ext.EventManager在生成内部id时通过El.addToCache产生的：
    /****************************************************************************************************
    El.addToCache = function(el, id){
        id = id || el.id;
        Ext.cache[id] = {
            el:  el,
            data: {},
            events: {}
        };
        return el;
    };
    *****************************************************************************************************/
    getElementEventCache: function (element) {
        var elementCache = Ext.cache[this.getId(element)];//取到Ext.cache对象中与element的id对应的对象
        return elementCache.events || (elementCache.events = {});
    },


    onDocumentReady: function (fn, scope, options) {
        var me = this,
            readyEvent = me.readyEvent,
            intervalId;

        if (Ext.isReady) {
            readyEvent || (readyEvent = new Ext.util.Event());
            readyEvent.addListener(fn, scope, options);
            readyEvent.fire();
            readyEvent.listeners = [];
        }
        else {
            if (!readyEvent) {
                readyEvent = me.readyEvent = new Ext.util.Event();


                var fireReady = function () {
                    Ext.isReady = true;


                    window.removeEventListener('load', arguments.callee, false);


                    if (intervalId) {
                        clearInterval(intervalId);
                    }



                    setTimeout(function () {
                        Ext.supports.init();

                        Ext.gesture.Manager.init();
                        Ext.orientation = Ext.Element.getOrientation();


                        readyEvent.fire({
                            orientation: Ext.orientation,
                            width: Ext.Element.getViewportWidth(),
                            height: Ext.Element.getViewportHeight()
                        });
                        readyEvent.listeners = [];
                    }, 50);
                };





                intervalId = setInterval(function () {
                    if (/loaded|complete/.test(document.readyState)) {
                        clearInterval(intervalId);
                        intervalId = null;
                        fireReady();
                    }
                }, 10);


                window.addEventListener('load', fireReady, false);
            }

            options = options || {};
            options.delay = options.delay || 1;
            readyEvent.addListener(fn, scope, options);
        }
    },


    onWindowResize: function (fn, scope, options) {
        var me = this,
            resizeEvent = me.resizeEvent;

        if (!resizeEvent) {
            me.resizeEvent = resizeEvent = new Ext.util.Event();
            var onResize = function () {
                resizeEvent.fire(Ext.Element.getViewportWidth(), Ext.Element.getViewportHeight());
            };
            this.addListener(window, 'resize', onResize, this);
        }

        resizeEvent.addListener(fn, scope, options);
    },

    onOrientationChange: function (fn, scope, options) {
        var me = this,
            orientationEvent = me.orientationEvent;

        if (!orientationEvent) {
            me.orientationEvent = orientationEvent = new Ext.util.Event();

            var onOrientationChange = function (viewport, size) {
                Ext.orientation = Ext.Viewport.getOrientation();

                orientationEvent.fire(Ext.orientation, size.width, size.height);
            };

            Ext.Viewport.on('resize', onOrientationChange, this);
        }

        orientationEvent.addListener(fn, scope, options);
    },

    unOrientationChange: function (fn, scope, options) {
        var me = this,
            orientationEvent = me.orientationEvent;

        if (orientationEvent) {
            orientationEvent.removeListener(fn, scope, options);
        }
    }
};


Ext.EventManager.on = Ext.EventManager.addListener;


Ext.EventManager.un = Ext.EventManager.removeListener;


Ext.onReady = Ext.EventManager.onDocumentReady;